package com.mobimap.nhom9.bus;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import android.database.sqlite.SQLiteDatabase;
import android.util.SparseArray;

import com.mobimap.nhom9.dao.AddressDAO;
import com.mobimap.nhom9.dao.IQueryHandler;
import com.mobimap.nhom9.dao.NodeInfoDAO;
import com.mobimap.nhom9.dao.NodeInfoDAO.RelativeNode;
import com.mobimap.nhom9.dao.PlaceDAO;
import com.mobimap.nhom9.dao.PlaceTypeDAO;
import com.mobimap.nhom9.dao.StreetSegmentDAO;
import com.mobimap.nhom9.dao.table.IDTOConverter;
import com.mobimap.nhom9.dao.table.StreetSegmentTable;
import com.mobimap.nhom9.dto.LocationDTO;
import com.mobimap.nhom9.dto.NodeInfoDTO;
import com.mobimap.nhom9.dto.PlaceExDTO;
import com.mobimap.nhom9.dto.PlaceTypeDTO;
import com.mobimap.nhom9.dto.StreetSegmentDTO;

class DefaultSearchEngine implements ISearchEngine {
	private SQLiteDatabase db;
	private AddressDAO addressDao;
	private PlaceDAO placeDao;

	public DefaultSearchEngine(SQLiteDatabase db) {
		this.db = db;
		this.addressDao = new AddressDAO(this.db);
		this.placeDao = new PlaceDAO(this.db);
	}

	/*
	 * search all place (in table Place and Address) that macthes input
	 * 
	 * @see com.mobimap.nhom9.bus.ISearchEngine#timDiaChi(java.lang.String)
	 */
	public List<PlaceExDTO> timDiaChi(String keyword) {

		List<PlaceExDTO> lstAddress = this.addressDao.timDiaChi(keyword);
		List<PlaceExDTO> lstPlace = this.placeDao.timDichVu(keyword);
		lstAddress.addAll(lstPlace);
		return lstAddress;
	}

	/*
	 * get all place (only in table Place) that nearest by a location
	 * 
	 * @see
	 * com.mobimap.nhom9.bus.ISearchEngine#timDichVuXungQuanh(com.mobimap.nhom9
	 * .dto.LocationDTO, java.lang.String, int)
	 */
	public List<PlaceExDTO> timDichVuXungQuanh(LocationDTO location,
			String dichVu, int limit) {

		PlaceTypeDAO placeTypeDao = new PlaceTypeDAO(this.db);
		List<PlaceTypeDTO> lstPlaceTypes = placeTypeDao
				.searchByDescription(dichVu);
		if (lstPlaceTypes != null) {
			if (lstPlaceTypes.size() > 0) {
				PlaceTypeDTO placeTypeDto = lstPlaceTypes.get(0);
				/** misstake? **/
				PlaceDAO placeDao = new PlaceDAO(this.db);
				List<PlaceExDTO> lstPlaces = placeDao
						.getPlacesByTypeAndDistance(placeTypeDto.getId(),
								location, limit);
				return lstPlaces;
			}
		}
		return Collections.emptyList();
	}

	public List<NodeInfoDTO> timDuong(LocationDTO diemBatDau,
			LocationDTO diemKetThuc) {

		GetRelativeHandler handler = new GetRelativeHandler(diemBatDau,
				diemKetThuc);
		handler.updateRelative();

		if (handler.getRelativeEndNode().isEmpty()
				|| handler.getRelativeEndNode().isEmpty())
			return null;
		NodeInfoDAO nodeDAO = new NodeInfoDAO(db);
		HashMap<NodeInfoDTO, NodeInfoDTO> openList = new HashMap<NodeInfoDTO, NodeInfoDTO>();
		HashMap<NodeInfoDTO, NodeInfoDTO> closeList = new HashMap<NodeInfoDTO, NodeInfoDTO>();
		HashSet<NodeInfoDTO> relativeNodesOfEnd = new HashSet<NodeInfoDTO>(
				handler.getRelativeEndNode().size());
		relativeNodesOfEnd.addAll(handler.getRelativeEndNode());

		NodeInfoDTO nodeStart = new NodeInfoDTO();
		nodeStart.setLocation(diemBatDau);

		NodeInfoDTO nodeEnd = new NodeInfoDTO();
		nodeEnd.setLocation(diemKetThuc);

		nodeStart.setD(0);
		nodeStart.updateHeuristic(nodeEnd);

		nodeEnd.setD(-1);
		nodeEnd.updateHeuristic(nodeEnd);

		closeList.put(nodeStart, nodeStart);
		NodeInfoDTO v = nodeStart;

		List<NodeInfoDTO> relativeNodes = handler.getRelativeStartNode();

		do {
			if (v.equals(nodeEnd)) {
				break;
			}
			if (relativeNodesOfEnd.contains(v))
				relativeNodes.add(nodeEnd);
			int len = relativeNodes.size();
			for (int i = 0; i < len; i++) {
				NodeInfoDTO node = relativeNodes.get(i);
				if (closeList.containsKey(node)) {
					continue;
				}
				NodeInfoDTO old = openList.get(node);
				if (old != null)
					node = old;
				else {
					node.updateHeuristic(nodeEnd);
					node.setParentNode(v);
				}
				double F = v.getD()
						+ LocationDTO.distance(v.getLocation(),
								node.getLocation());
				if (node.getD() == -1 || F < node.getD()) {
					node.setD(F);
					node.setParentNode(v);
				}
				openList.put(node, node);
			}

			v = null;
			Collection<NodeInfoDTO> openNodes = openList.values();
			for (NodeInfoDTO node : openNodes) {
				if (v == null) {
					v = node;

				} else if (node.getD() != -1
						&& v.getD() + v.getHeuristic() > node.getD()
								+ node.getHeuristic()) {
					v = node;
				}
			}
			if (v == null)
				return null;
			openList.remove(v);
			closeList.put(v, v);
			relativeNodes = nodeDAO.getRelativeNodes(v,
					NodeInfoDAO.RelativeNode.GoTo);
		} while (true);
		List<NodeInfoDTO> kq = new ArrayList<NodeInfoDTO>();
		NodeInfoDTO n = v;
		do {
			kq.add(n);
			n = n.getParentNode();
		} while (n != null);
		Collections.reverse(kq);
		return kq;
	}

	private static SparseArray<NodeInfoDTO> cacheNodes = new SparseArray<NodeInfoDTO>();

	private static NodeInfoDTO getNodeCache(NodeInfoDAO dao, int key) {
		NodeInfoDTO node = null;
		if (cacheNodes.indexOfKey(key) < 0) {
			node = dao.getDTO(key);
			cacheNodes.put(key, node);
		} else {
			node = cacheNodes.get(key);
		}
		return node;
	}

	class GetRelativeHandler implements IQueryHandler<StreetSegmentDTO> {
		private List<NodeInfoDTO> relativeStartNode;
		private List<NodeInfoDTO> relativeEndNode;
		private LocationDTO startLocation;
		private double cosMaxAngleStart;
		private double cosMaxAngleEnd;
		private LocationDTO endLocation;

		private NodeInfoDAO nodeInfoDAO;
		private final IDTOConverter<StreetSegmentDTO> converter = new StreetSegmentTable();

		private boolean markGetEnd;
		private boolean markGetStart;

		public GetRelativeHandler(LocationDTO start, LocationDTO end) {
			this.relativeStartNode = new ArrayList<NodeInfoDTO>();
			this.relativeEndNode = new ArrayList<NodeInfoDTO>();
			this.startLocation = start;
			this.endLocation = end;
			this.nodeInfoDAO = new NodeInfoDAO(DefaultSearchEngine.this.db);
			this.markGetStart = true;
			this.markGetEnd = true;

		}

		public void updateRelative() {
			NodeInfoDTO nodeStart = nodeInfoDAO.getDTO(startLocation);
			if (nodeStart != null) {
				nodeInfoDAO.getRelativeNodes(nodeStart, RelativeNode.GoTo);
				markGetStart = false;
			}

			NodeInfoDTO nodeEnd = nodeInfoDAO.getDTO(startLocation);
			if (nodeEnd != null) {
				nodeInfoDAO.getRelativeNodes(nodeEnd, RelativeNode.GoFrom);
				markGetEnd = false;
			}
			if ((markGetStart | markGetEnd) == false)
				return;

			StreetSegmentDAO streetSegmentDAO = new StreetSegmentDAO(db);
			streetSegmentDAO.getDTOs(this);
		}

		public IDTOConverter<StreetSegmentDTO> GetConverter() {

			return converter;
		}

		public List<NodeInfoDTO> getRelativeStartNode() {
			return relativeStartNode;
		}

		public List<NodeInfoDTO> getRelativeEndNode() {
			return relativeEndNode;
		}

		public boolean handle(StreetSegmentDTO dto) {
			int start = dto.getStartPoint();
			int end = dto.getEndPoint();
			NodeInfoDTO nodeStart = getNodeCache(nodeInfoDAO, start);
			NodeInfoDTO nodeEnd = getNodeCache(nodeInfoDAO, end);

			double d = LocationDTO.distance(nodeStart.getLocation(),
					nodeEnd.getLocation());
			if (markGetStart)
				addRelativeNode(relativeStartNode, startLocation, nodeStart,
						nodeEnd, dto.isTwoWay(), d, cosMaxAngleStart,
						RelativeNode.GoTo);
			if (markGetEnd)
				addRelativeNode(relativeEndNode, endLocation, nodeStart,
						nodeEnd, dto.isTwoWay(), d, cosMaxAngleEnd,
						RelativeNode.GoFrom);
			return false;
		}

		private void addRelativeNode(List<NodeInfoDTO> relativeNodes,
				LocationDTO location, NodeInfoDTO nodeStart,
				NodeInfoDTO nodeEnd, boolean isTwoWay, double d,
				double cosMaxAngle, RelativeNode relative) {
			if (LocationDTO.distance(location, nodeStart.getLocation()) > d
					|| LocationDTO.distance(location, nodeEnd.getLocation()) > d)
				return;

			// KhÃ´ng dÃ¹ng Math.acos Ä‘á»ƒ tÃ­nh nháº±m tÄƒng Ä‘á»™ chÃ­nh xÃ¡c
			double cosAngle = calculateCosAngle(nodeStart.getLocation(),
					location, nodeEnd.getLocation());
			// Cosin nghich bien tu 0 - > 180
			if (cosAngle > cosMaxAngle)
				return;
			if (cosAngle < cosMaxAngle) {
				relativeNodes.clear();
				cosMaxAngle = cosAngle;
			}
			if (relative == RelativeNode.GoTo) {
				relativeNodes.add(nodeEnd);
				if (isTwoWay)
					relativeNodes.add(nodeStart);
			} else {
				relativeNodes.add(nodeStart);
				if (isTwoWay)
					relativeNodes.add(nodeEnd);
			}
		}

		private double calculateCosAngle(LocationDTO a, LocationDTO b,
				LocationDTO c) {
			double ab = LocationDTO.distance(a, b);
			double ac = LocationDTO.distance(a, c);
			double bc = LocationDTO.distance(b, c);
			if (ab == 0 || bc == 0) {
				return -1;
			}
			if (ac == 0) {
				// Ä�á»� phÃ²ng trÆ°á»�ng há»£p nháº­p liá»‡u Ä‘iá»ƒm Ä‘áº§u trÃ¹ng Ä‘iá»ƒm cuá»‘i
				return 1;
			}

			return ((ab * ab + bc * bc - ac * ac) / (2 * ab * bc)); // Ä�á»‹nh lÃ­
																	// cos trong
																	// tam giÃ¡c
		}

		public boolean isInterrupt() {

			return false;
		}

		public void setInterrupt(boolean interrupt) {

		}
	}
}
